﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using AppEnvironment.Lib.Interfaces;
using AppEnvironment.Web;

namespace AppEnvironment.Lib
{
    [Statefull(StateTypeEnum.Thread)]
    public class EventTracer<TDataScope, TDataContext>
        where TDataScope : class, ITraceableDataContextScope<TDataContext>, IDisposable, new()
        where TDataContext : ITraceableDataContext
    {
        List<EventLineDto> UpdateQueue;
        List<EventLineDto> InsertQueue;

        public event Action<TDataContext, IEnumerable<EventDto>> OnAddEvents = delegate { };

        public EventTracer()
        {
            UpdateQueue = new List<EventLineDto>();
            InsertQueue = new List<EventLineDto>();

            App.CurrentThread.ForType<TDataScope>().OnAfterNew += (e) =>
            {
                BindToDataContext(e.InstanceCreated);
            };

            var webEvents = App.New<WebEvents>();
            webEvents.OnEndRequest += () => { Flush(); };
            webEvents.OnError += (e) => { Flush(); };
        }


        private void Flush()
        {
            if ((UpdateQueue.Count > 0) || (InsertQueue.Count > 0))
            {
                using (var data = App.New<TDataScope>())
                {
                    data.Context.Items["_EventTracer_flushing"] = true;
                    try
                    {
                        BeforeContextSubmit(data.Context);
                        data.Context.SubmitChanges();
                        AfterContextSubmit(data.Context);
                    }
                    finally
                    {
                        data.Context.Items.Remove("_EventTracer_flushing");
                    }
                }
            }
        }

        void BindToDataContext(TDataScope scope)
        {
            TDataContext context = scope.Context;
            if (context != null)
            {
                if (!context.Items.ContainsKey("_EventTracer_attached"))
                {
                    context.OnBeforeSubmit += () => { if (!context.Items.ContainsKey("_EventTracer_flushing")) BeforeContextSubmit(context); };
                    context.OnAfterSubmit += () => { if (!context.Items.ContainsKey("_EventTracer_flushing")) AfterContextSubmit(context); };
                    context.Items["_EventTracer_attached"] = true;
                }
            }
        }

        private void AfterContextSubmit(TDataContext context)
        {
            if (InsertQueue.Count > 0)
            {
                OnAddEvents(context, GetEvents(InsertQueue));
                InsertQueue.Clear();
                context.SubmitChanges();
            }
        }

        private void BeforeContextSubmit(TDataContext context)
        {
            if (UpdateQueue.Count > 0)
            {
                OnAddEvents(context, GetEvents(UpdateQueue));
                UpdateQueue.Clear();
            }
        }

        public void RegisterEventSource<T>(ITraceEventSource<T> source)
        {
            source.OnTraceEvent += (obj, e) =>
            {
                var dataObject = obj as IDataObjectWithId;
                var eventLine = new EventLineDto
                {
                    EventName = e.ToString(),
                    ObjectName = NameByConvention<T>(),
                    GetDetail = () => GetDetailForEvent<T>(obj, e),
                    GetObjectId = () => { return dataObject == null ? "" : dataObject.Id.ToString(); },
                };

                if (dataObject == null)
                {
                    UpdateQueue.Add(eventLine);
                }
                else
                {
                    if (dataObject.Id > 0)
                    {
                        UpdateQueue.Add(eventLine);
                    }
                    else
                    {
                        InsertQueue.Add(eventLine);
                    }
                }
            };
        }

        private static string GetDetailForEvent<T>(ITraceableObject<T> obj, T e)
        {
            string result = "";

            if (obj != null)
            {
                try
                {
                    result = obj.GetDetailForEvent(e);
                }
                catch (Exception ex)
                {
                    //this must be a catch-all, because its a logging side-effect, and error will be made clear here in log:
                    result = string.Format("ERROR in {0}.GetDetailForEvent({1}): {2}", obj.GetType(), e.ToString(), ex.ToString());
                }
            }

            return result;
        }

        private IEnumerable<EventDto> GetEvents(IEnumerable<EventLineDto> list)
        {
            foreach (var item in list)
            {
                yield return new EventDto
                {
                    EventDetail = item.GetDetail(),
                    EventName = item.EventName,
                    ObjectId = item.GetObjectId(),
                    ObjectName = item.ObjectName,
                };
            }
        }

        private const string EventTypeEnumConst = "EventTypeEnum";
        private string NameByConvention<T>()
        {
            string name = typeof(T).Name;
            if (name.IndexOf(EventTypeEnumConst) > 0)
            {
                name = name.Substring(0, name.Length - EventTypeEnumConst.Length);
            }
            return name;
        }

        private class EventLineDto
        {
            public string EventName { get; set; }
            public string ObjectName { get; set; }
            public Func<string> GetObjectId { get; set; }
            public Func<string> GetDetail { get; internal set; }
        }

        public class EventDto
        {
            public string ObjectId { get; set; }
            public string ObjectName { get; set; }
            public string EventName { get; set; }
            public string EventDetail { get; set; }
        }


    }

    public class EventTracerDescriber<T> : ITraceableObject<T>
    {
        Func<string> GetDescription;

        public static ITraceableObject<T> Make(Func<string> getDescription)
        {
            return new EventTracerDescriber<T>(getDescription);
        }

        EventTracerDescriber(Func<string> getDescription)
        {
            GetDescription = getDescription;
        }

        string ITraceableObject<T>.GetDetailForEvent(T eventType)
        {
            return GetDescription();
        }
    }

    public interface ITraceEventSource<T>
    {
        event TraceEventDelegate<T> OnTraceEvent;
    }

    public delegate void TraceEventDelegate<T>(ITraceableObject<T> dataObject, T eventType);


    public interface ITraceableObject<T>
    {
        string GetDetailForEvent(T eventType);
    }

    public interface IDataObjectWithId
    {
        int Id { get; set; }
    }

    public interface ITraceableObjectWithId<T> : ITraceableObject<T>, IDataObjectWithId
    {
    }

    public interface ITraceableDataContextScope<TDataContext>
        where TDataContext : ITraceableDataContext
    {
        TDataContext Context { get; }
    }

    public interface ITraceableDataContext
    {
        Hashtable Items { get; }
        event Action OnBeforeSubmit;
        event Action OnAfterSubmit;
        void SubmitChanges();
    }
}
